/* net.cc
   Mathieu Stefani, 12 August 2015

*/

#include <pistache/common.h>
#include <pistache/config.h>
#include <pistache/net.h>

#include <cassert>
#include <cstring>
#include <limits>
#include <stdexcept>

#include <arpa/inet.h>
#include <ifaddrs.h>
#include <iostream>
#include <netinet/in.h>

namespace Pistache {

namespace {
IP GetIPv4(const std::string &host) {
  in_addr addr;
  int res = inet_pton(AF_INET, host.c_str(), &addr);
  if (res == 0) {
      throw std::invalid_argument("Invalid IPv4 network address");
  } else if (res < 0) {
      throw std::invalid_argument(strerror(errno));
  } else {
    struct sockaddr_in s_addr = {0};
    s_addr.sin_family = AF_INET;

    static_assert(sizeof(s_addr.sin_addr.s_addr) >= sizeof(uint32_t));
    memcpy(&(s_addr.sin_addr.s_addr), &addr.s_addr, sizeof(uint32_t));

    return IP(reinterpret_cast<struct sockaddr *>(&s_addr));
  }
}

IP GetIPv6(const std::string &host) {
  in6_addr addr6;
  int res = inet_pton(AF_INET6, host.c_str(), &(addr6.s6_addr16));
  if (res == 0) {
      throw std::invalid_argument("Invalid IPv6 network address");
  } else if (res < 0) {
      throw std::invalid_argument(strerror(errno));
  } else {
  struct sockaddr_in6 s_addr = {0};
  s_addr.sin6_family = AF_INET6;

  static_assert(sizeof(s_addr.sin6_addr.s6_addr16) >= 8 * sizeof(uint16_t));
  memcpy(&(s_addr.sin6_addr.s6_addr16), &addr6.s6_addr16,
         8 * sizeof(uint16_t));

  return IP(reinterpret_cast<struct sockaddr *>(&s_addr));
  }
}
} // namespace

Port::Port(uint16_t port) : port(port) {}

Port::Port(const std::string &data) {
  if (data.empty())
    throw std::invalid_argument("Invalid port: empty port");
  char *end = 0;
  long port_num = strtol(data.c_str(), &end, 10);
  if (*end != 0 || port_num < Port::min() || port_num > Port::max())
    throw std::invalid_argument("Invalid port: " + data);
  port = static_cast<uint16_t>(port_num);
}

bool Port::isReserved() const { return port < 1024; }

bool Port::isUsed() const {
  throw std::runtime_error("Unimplemented");
  return false;
}

std::string Port::toString() const { return std::to_string(port); }

IP::IP() {
  family = AF_INET;
  addr = {0};
  addr.sin_family = AF_INET;
  uint8_t buff[INET_ADDRSTRLEN + 1] = {0, 0, 0, 0};
  memcpy(&addr.sin_addr.s_addr, buff, INET_ADDRSTRLEN);
}

IP::IP(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
  family = AF_INET;
  addr = {0};
  addr.sin_family = AF_INET;
  uint8_t buff[INET_ADDRSTRLEN + 1] = {a, b, c, d};
  memcpy(&addr.sin_addr.s_addr, buff, INET_ADDRSTRLEN);
}

IP::IP(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f,
       uint16_t g, uint16_t h) {
  family = AF_INET6;
  addr6 = {0};
  addr6.sin6_family = AF_INET6;
  uint16_t buff[9]{a, b, c, d, e, f, g, h, '\0'};
  uint16_t remap[9] = {0, 0, 0, 0, 0, 0, 0, 0, '\0'};
  if (htonl(1) != 1) {
    for (int i = 0; i < 8; i++) {
      uint16_t x = buff[i];
      uint16_t y = htons(x);
      remap[i] = y;
    }
  } else {
    memcpy(&remap, &buff, sizeof(remap));
  }
  memcpy(&addr6.sin6_addr.s6_addr16, &remap, 8 * sizeof(uint16_t));
}

IP::IP(struct sockaddr *_addr) {
  if (_addr->sa_family == AF_INET) {
    struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(_addr);
    family = AF_INET;
    port = in_addr->sin_port;
    memcpy(&(addr.sin_addr.s_addr), &(in_addr->sin_addr.s_addr),
           sizeof(in_addr_t));
  } else if (_addr->sa_family == AF_INET6) {
    struct sockaddr_in6 *in_addr =
        reinterpret_cast<struct sockaddr_in6 *>(_addr);
    family = AF_INET6;
    port = in_addr->sin6_port;
    memcpy(&(addr6.sin6_addr.s6_addr16), &(in_addr->sin6_addr.s6_addr16),
           8 * sizeof(uint16_t));
  }
}

IP IP::any() { return IP(0, 0, 0, 0); }

IP IP::any(bool is_ipv6) {
  if (is_ipv6) {
    return IP(0, 0, 0, 0, 0, 0, 0, 0);
  } else {
    return IP(0, 0, 0, 0);
  }
}

IP IP::loopback() { return IP(127, 0, 0, 1); }

IP IP::loopback(bool is_ipv6) {
  if (is_ipv6) {
    return IP(0, 0, 0, 0, 0, 0, 0, 1);
  } else {
    return IP(127, 0, 0, 1);
  }
}

int IP::getFamily() const { return family; }

int IP::getPort() const { return port; }

std::string IP::toString() const {
  char buff[INET6_ADDRSTRLEN + 1];
  if (family == AF_INET) {
    in_addr_t addr_;
    toNetwork(&addr_);
    inet_ntop(AF_INET, &addr_, buff, INET_ADDRSTRLEN);
  } else if (family == AF_INET6) {
    struct in6_addr addr_ = in6addr_any;
    toNetwork(&addr_);
    inet_ntop(AF_INET6, &addr_, buff, INET6_ADDRSTRLEN);
  }
  return std::string(buff);
}

void IP::toNetwork(in_addr_t *_addr) const {
  memcpy(_addr, &addr.sin_addr.s_addr, sizeof(uint32_t));
}

void IP::toNetwork(struct in6_addr *out) const {
  memcpy(&out->s6_addr16, &(addr6.sin6_addr.s6_addr16), 8 * sizeof(uint16_t));
}

bool IP::supported() {
  struct ifaddrs *ifaddr = nullptr;
  struct ifaddrs *ifa = nullptr;
  int addr_family, n;
  bool supportsIpv6 = false;

  if (getifaddrs(&ifaddr) == -1) {
    throw std::runtime_error("Call to getifaddrs() failed");
  }

  for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) {
    if (ifa->ifa_addr == nullptr) {
      continue;
    }

    addr_family = ifa->ifa_addr->sa_family;
    if (addr_family == AF_INET6) {
      supportsIpv6 = true;
      continue;
    }
  }

  freeifaddrs(ifaddr);
  return supportsIpv6;
}

AddressParser::AddressParser(const std::string &data) {
  std::size_t end_pos = data.find(']');
  std::size_t start_pos = data.find('[');
  if (start_pos != std::string::npos && end_pos != std::string::npos &&
      start_pos < end_pos) {
    std::size_t colon_pos = data.find_first_of(':', end_pos);
    if (colon_pos != std::string::npos) {
      hasColon_ = true;
    }
    host_ = data.substr(start_pos, end_pos + 1);
    family_ = AF_INET6;
    ++end_pos;
  } else {
    std::size_t colon_pos = data.find(':');
    if (colon_pos != std::string::npos) {
      hasColon_ = true;
    }
    end_pos = colon_pos;
    host_ = data.substr(0, end_pos);
    family_ = AF_INET;
  }

  if (end_pos != std::string::npos && hasColon_) {
    port_ = data.substr(end_pos + 1);
    if (port_.empty())
      throw std::invalid_argument("Invalid port");
  }
}

const std::string &AddressParser::rawHost() const { return host_; }

const std::string &AddressParser::rawPort() const { return port_; }

bool AddressParser::hasColon() const { return hasColon_; }

int AddressParser::family() const { return family_; }

Address::Address() : ip_{}, port_{0} {}

Address::Address(std::string host, Port port) {
  std::string addr = std::move(host);
  addr.append(":");
  addr.append(port.toString());
  init(std::move(addr));
}

Address::Address(std::string addr) { init(std::move(addr)); }

Address::Address(const char *addr) { init(std::string(addr)); }

Address::Address(IP ip, Port port) : ip_(ip), port_(port) {}

Address Address::fromUnix(struct sockaddr *addr) {
  if ((addr->sa_family == AF_INET) or (addr->sa_family == AF_INET6)) {
    IP ip = IP(addr);
    Port port = Port(ip.getPort());
    assert(addr);
    return Address(ip, port);
  }
  throw Error("Not an IP socket");
}

Address Address::fromUnix(struct sockaddr_in *addr) {
  return Address::fromUnix(reinterpret_cast<struct sockaddr *>(addr));
}

std::string Address::host() const { return ip_.toString(); }

Port Address::port() const { return port_; }

int Address::family() const { return ip_.getFamily(); }

void Address::init(const std::string &addr) {
  AddressParser parser(addr);
  int family_ = parser.family();
  std::string host_;
  if (family_ == AF_INET6) {
    const std::string &raw_host = parser.rawHost();
    assert(raw_host.size() > 2);
    host_ = addr.substr(1, raw_host.size() - 2);

    ip_ = GetIPv6(host_);
  } else if (family_ == AF_INET) {
    host_ = parser.rawHost();

    if (host_ == "*") {
      host_ = "0.0.0.0";
    } else if (host_ == "localhost") {
      host_ = "127.0.0.1";
    }

    struct hostent *hp = ::gethostbyname(host_.c_str());

    if (hp) {
        char **addr = hp->h_addr_list;
        while (*addr) {
            host_ = std::string(inet_ntoa(*reinterpret_cast<struct in_addr *>(*addr)));
            break;
        }
    }

    ip_ = GetIPv4(host_);
  } else {
    assert(false);
  }

  const std::string &portPart = parser.rawPort();
  if (portPart.empty()) {
    if (parser.hasColon()) {
      // "www.example.com:" or "127.0.0.1:" cases
      throw std::invalid_argument("Invalid port");
    } else {
      // "www.example.com" or "127.0.0.1" cases
      port_ = Const::HTTP_STANDARD_PORT;
    }
  } else {
    char *end = 0;
    long port = strtol(portPart.c_str(), &end, 10);
    if (*end != 0 || port < Port::min() || port > Port::max())
      throw std::invalid_argument("Invalid port");
    port_ = Port(port);
  }
}

Error::Error(const char *message) : std::runtime_error(message) {}

Error::Error(std::string message) : std::runtime_error(std::move(message)) {}

Error Error::system(const char *message) {
  const char *err = strerror(errno);

  std::string str(message);
  str += ": ";
  str += err;

  return Error(std::move(str));
}

} // namespace Pistache
